{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial 05: Creating Custom Networks\n",
    "\n",
    "This tutorial walks you through the process of generating custom networks. Networks define the network geometry of a task, as well as the constituents of the network, e.g. vehicles, traffic lights, etc... Various networks are available in Flow, depicting a diverse set of open and closed traffic networks such as ring roads, intersections, traffic light grids, straight highway merges, and more. \n",
    "\n",
    "In this exercise, we will recreate the ring road network, seen in the figure below.\n",
    "\n",
    "<img src=\"img/ring_network.png\">\n",
    "\n",
    "In order to recreate this network, we will design a *network* class. This class creates the configuration files needed to produce a transportation network within the simulator. It also specifies the location of edge nodes in the network, as well as the positioning of vehicles at the start of a run.\n",
    "\n",
    "We begin by creating a class that inherits the methods of Flow's base network class. The separate methods are filled in in later sections."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import Flow's base network class\n",
    "from flow.networks import Network\n",
    "\n",
    "# define the network class, and inherit properties from the base network class\n",
    "class myNetwork(Network):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The rest of the tutorial is organized as follows: sections 1 and 2 walk through the steps needed to specify custom traffic network geometry features and auxiliary features, respectively, while section 3 implements the new network in a simulation for visualization and testing purposes.\n",
    "\n",
    "## 1. Specifying Traffic Network Features\n",
    "\n",
    "One of the core responsibilities of the network class is to to generate the necessary xml files needed to initialize a sumo instance. These xml files describe specific network features such as the position and directions of nodes and edges (see the figure above). Once the base network has been inherited, specifying these features becomes very systematic. All child classes are required to define at least the following three methods: \n",
    "\n",
    "* **specify_nodes**: specifies the attributes of nodes in the network\n",
    "* **specify_edges**: specifies the attributes of edges containing pairs on nodes in the network\n",
    "* **specify_routes**: specifies the routes vehicles can take starting from any edge\n",
    "\n",
    "Additionally, the following optional functions may also be defined:\n",
    "\n",
    "* **specify_types**: specifies the attributes of various edge types (if any exist)\n",
    "* **specify_connections**: specifies the attributes of connections. These attributes are used to describe how any specific node's incoming and outgoing edges/lane pairs are connected. If no connections are specified, sumo generates default connections.\n",
    "\n",
    "All of the functions mentioned above paragraph take in as input `net_params`, and output a list of dictionary elements, with each element providing the attributes of the component to be specified.\n",
    "\n",
    "This tutorial will cover the first three methods. For examples of `specify_types` and `specify_routes`, refer to source code located in `flow/networks/ring.py` and `flow/networks/bridge_toll.py`, respectively."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 ADDITIONAL_NET_PARAMS\n",
    "\n",
    "The features used to parametrize the network are specified within the `NetParams` input, as discussed in tutorial 1. Specifically, for the sake of our network, the `additional_params` attribute within `NetParams` will be responsible for storing information on the radius, number of lanes, and speed limit within each lane, as seen in the figure above. Accordingly, for this problem, we define an `ADDITIONAL_NET_PARAMS` variable of the form:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "ADDITIONAL_NET_PARAMS = {\n",
    "    \"radius\": 40,\n",
    "    \"num_lanes\": 1,\n",
    "    \"speed_limit\": 30,\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All networks presented in Flow provide a unique `ADDITIONAL_NET_PARAMS` component containing the information needed to properly define the network parameters of the network. We assume that these values are always provided by the user, and accordingly can be called from `net_params`. For example, if we would like to call the \"radius\" parameter, we simply type:\n",
    "\n",
    "    radius = net_params.additional_params[\"radius\"]\n",
    "\n",
    "### 1.2 specify_nodes\n",
    "\n",
    "The nodes of a network are the positions of a select few points in the network. These points are connected together using edges (see section 1.4). In order to specify the location of the nodes that will be placed in the network, the function `specify_nodes` is used. This method returns a list of dictionary elements, where each dictionary depicts the attributes of a single node. These node attributes include:  \n",
    "* **id**: name of the node\n",
    "* **x**: x coordinate of the node\n",
    "* **y**: y coordinate of the node\n",
    "* other sumo-related attributes, see: http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Node_Descriptions\n",
    "\n",
    "Refering to the figure at the top of this tutorial, we specify four nodes at the bottom (0,-r), top (0,r), left (-r,0), and right (0,r) of the ring. This is done as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class myNetwork(myNetwork):  # update my network class\n",
    "\n",
    "    def specify_nodes(self, net_params):\n",
    "        # one of the elements net_params will need is a \"radius\" value\n",
    "        r = net_params.additional_params[\"radius\"]\n",
    "\n",
    "        # specify the name and position (x,y) of each node\n",
    "        nodes = [{\"id\": \"bottom\", \"x\": 0,  \"y\": -r},\n",
    "                 {\"id\": \"right\",  \"x\": r,  \"y\": 0},\n",
    "                 {\"id\": \"top\",    \"x\": 0,  \"y\": r},\n",
    "                 {\"id\": \"left\",   \"x\": -r, \"y\": 0}]\n",
    "\n",
    "        return nodes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 specify_edges\n",
    "\n",
    "Once the nodes are specified, the nodes are linked together using directed edges. This done through the `specify_edges` method which, similar to `specify_nodes`, returns a list of dictionary elements, with each dictionary specifying the attributes of a single edge. The attributes include:\n",
    "\n",
    "* **id**: name of the edge\n",
    "* **from**: name of the node the edge starts from\n",
    "* **to**: the name of the node the edges ends at\n",
    "* **length**: length of the edge\n",
    "* **numLanes**: the number of lanes on the edge\n",
    "* **speed**: the speed limit for vehicles on the edge\n",
    "* other sumo-related attributes, see: http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Edge_Descriptions.\n",
    "\n",
    "One useful additional attribute is **shape**, which specifies the shape of the edge connecting the two nodes. The shape consists of a series of subnodes (internal to sumo) that are connected together by straight lines to create a curved edge. If no shape is specified, the nodes are connected by a straight line. This attribute will be needed to create the circular arcs between the nodes in the system. \n",
    "\n",
    "We now create four arcs connected the nodes specified in section 1.2, with the direction of the edges directed counter-clockwise:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# some mathematical operations that may be used\n",
    "from numpy import pi, sin, cos, linspace\n",
    "\n",
    "class myNetwork(myNetwork):  # update my network class\n",
    "\n",
    "    def specify_edges(self, net_params):\n",
    "        r = net_params.additional_params[\"radius\"]\n",
    "        edgelen = r * pi / 2\n",
    "        # this will let us control the number of lanes in the network\n",
    "        lanes = net_params.additional_params[\"num_lanes\"]\n",
    "        # speed limit of vehicles in the network\n",
    "        speed_limit = net_params.additional_params[\"speed_limit\"]\n",
    "\n",
    "        edges = [\n",
    "            {\n",
    "                \"id\": \"edge0\",\n",
    "                \"numLanes\": lanes,\n",
    "                \"speed\": speed_limit,     \n",
    "                \"from\": \"bottom\", \n",
    "                \"to\": \"right\", \n",
    "                \"length\": edgelen,\n",
    "                \"shape\": [(r*cos(t), r*sin(t)) for t in linspace(-pi/2, 0, 40)]\n",
    "            },\n",
    "            {\n",
    "                \"id\": \"edge1\",\n",
    "                \"numLanes\": lanes, \n",
    "                \"speed\": speed_limit,\n",
    "                \"from\": \"right\",\n",
    "                \"to\": \"top\",\n",
    "                \"length\": edgelen,\n",
    "                \"shape\": [(r*cos(t), r*sin(t)) for t in linspace(0, pi/2, 40)]\n",
    "            },\n",
    "            {\n",
    "                \"id\": \"edge2\",\n",
    "                \"numLanes\": lanes,\n",
    "                \"speed\": speed_limit,\n",
    "                \"from\": \"top\",\n",
    "                \"to\": \"left\", \n",
    "                \"length\": edgelen,\n",
    "                \"shape\": [(r*cos(t), r*sin(t)) for t in linspace(pi/2, pi, 40)]},\n",
    "            {\n",
    "                \"id\": \"edge3\", \n",
    "                \"numLanes\": lanes, \n",
    "                \"speed\": speed_limit,\n",
    "                \"from\": \"left\", \n",
    "                \"to\": \"bottom\", \n",
    "                \"length\": edgelen,\n",
    "                \"shape\": [(r*cos(t), r*sin(t)) for t in linspace(pi, 3*pi/2, 40)]\n",
    "            }\n",
    "        ]\n",
    "\n",
    "        return edges"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4 specify_routes\n",
    "\n",
    "The routes are the sequence of edges vehicles traverse given their current position. For example, a vehicle beginning in the edge titled \"edge0\" (see section 1.3) must traverse, in sequence, the edges \"edge0\", \"edge1\", \"edge2\", and \"edge3\", before restarting its path.\n",
    "\n",
    "In order to specify the routes a vehicle may take, the function `specify_routes` is used. The routes in this method can be specified in one of three ways:\n",
    "\n",
    "**1. Single route per edge:**\n",
    "\n",
    "In this case of deterministic routes (as is the case in the ring road network), the routes can be specified as dictionary where the key element represents the starting edge and the element is a single list of edges the vehicle must traverse, with the first edge corresponding to the edge the vehicle begins on. Note that the edges must be connected for the route to be valid.\n",
    "\n",
    "For this network, the available routes under this setting can be defined as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class myNetwork(myNetwork):  # update my network class\n",
    "\n",
    "    def specify_routes(self, net_params):\n",
    "        rts = {\"edge0\": [\"edge0\", \"edge1\", \"edge2\", \"edge3\"],\n",
    "               \"edge1\": [\"edge1\", \"edge2\", \"edge3\", \"edge0\"],\n",
    "               \"edge2\": [\"edge2\", \"edge3\", \"edge0\", \"edge1\"],\n",
    "               \"edge3\": [\"edge3\", \"edge0\", \"edge1\", \"edge2\"]}\n",
    "\n",
    "        return rts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**2. Multiple routes per edge:**\n",
    "\n",
    "Alternatively, if the routes are meant to be stochastic, each element can consist of a list of (route, probability) tuples, where the first element in the tuple is one of the routes a vehicle can take from a specific starting edge, and the second element is the probability that vehicles will choose that route. Note that, in this case, the sum of probability values for each dictionary key must sum up to one.\n",
    "\n",
    "For example, modifying the code snippet we presented above, another valid way of representing the route in a more probabilistic setting is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class myNetwork(myNetwork):  # update my network class\n",
    "\n",
    "    def specify_routes(self, net_params):\n",
    "        rts = {\"edge0\": [([\"edge0\", \"edge1\", \"edge2\", \"edge3\"], 1)],\n",
    "               \"edge1\": [([\"edge1\", \"edge2\", \"edge3\", \"edge0\"], 1)],\n",
    "               \"edge2\": [([\"edge2\", \"edge3\", \"edge0\", \"edge1\"], 1)],\n",
    "               \"edge3\": [([\"edge3\", \"edge0\", \"edge1\", \"edge2\"], 1)]}\n",
    "\n",
    "        return rts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**3. Per-vehicle routes:**\n",
    "\n",
    "Finally, if you would like to assign a specific starting route to a vehicle with a specific ID, you can do so by adding a element into the dictionary whose key is the name of the vehicle and whose content is the list of edges the vehicle is meant to traverse as soon as it is introduced to the network.\n",
    "\n",
    "As an example, assume we have a vehicle named \"human_0\" in the network (as we will in the later sections), and it is initialized in the edge names \"edge_0\". Then, the route for this edge specifically can be added through the `specify_routes` method as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class myNetwork(myNetwork):  # update my network class\n",
    "\n",
    "    def specify_routes(self, net_params):\n",
    "        rts = {\"edge0\": [\"edge0\", \"edge1\", \"edge2\", \"edge3\"],\n",
    "               \"edge1\": [\"edge1\", \"edge2\", \"edge3\", \"edge0\"],\n",
    "               \"edge2\": [\"edge2\", \"edge3\", \"edge0\", \"edge1\"],\n",
    "               \"edge3\": [\"edge3\", \"edge0\", \"edge1\", \"edge2\"],\n",
    "               \"human_0\": [\"edge0\", \"edge1\", \"edge2\", \"edge3\"]}\n",
    "\n",
    "        return rts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In all three cases, the routes are ultimately represented in the class in the form described under the multiple routes setting, i.e.\n",
    "\n",
    "    >>> print(network.rts)\n",
    "\n",
    "    {\n",
    "        \"edge0\": [\n",
    "            ([\"edge0\", \"edge1\", \"edge2\", \"edge3\"], 1)\n",
    "        ],\n",
    "        \"edge1\": [\n",
    "            ([\"edge1\", \"edge2\", \"edge3\", \"edge0\"], 1)\n",
    "        ],\n",
    "        \"edge2\": [\n",
    "            ([\"edge2\", \"edge3\", \"edge0\", \"edge1\"], 1)\n",
    "        ],\n",
    "        \"edge3\": [\n",
    "            ([\"edge3\", \"edge0\", \"edge1\", \"edge2\"], 1)\n",
    "        ],\n",
    "        \"human_0\": [\n",
    "            ([\"edge0\", \"edge1\", \"edge2\", \"edge3\"], 1)\n",
    "        ]\n",
    "    }\n",
    "\n",
    "where the vehicle-specific route is only included in the third case."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Specifying Auxiliary Network Features\n",
    "\n",
    "Other auxiliary methods exist within the base network class to help support vehicle state initialization and acquisition. Of these methods, the only required abstract method is:\n",
    "\n",
    "* **specify_edge_starts**: defines edge starts for road sections with respect to some global reference\n",
    "\n",
    "Other optional abstract methods within the base network class include:\n",
    "\n",
    "* **specify_internal_edge_starts**: defines the edge starts for internal edge nodes caused by finite length connections between road section\n",
    "* **specify_intersection_edge_starts**: defines edge starts for intersections with respect to some global reference frame. Only needed by environments with intersections.\n",
    "* **gen_custom_start_pos**: used to generate a user defined set of starting positions for vehicles in the network\n",
    "\n",
    "### 2.2 Specifying the Starting Position of Edges\n",
    "\n",
    "All of the above functions starting with \"specify\" receive no inputs, and return a list of tuples in which the first element of the tuple is the name of the edge/intersection/internal_link, and the second value is the distance of the link from some global reference, i.e. [(link_0, pos_0), (link_1, pos_1), ...].\n",
    "\n",
    "The data specified in `specify_edge_starts` is used to provide a \"global\" sense of the location of vehicles, in one dimension. This is done either through the `get_x_by_id` method within an environment, or the `get_absolute_position` method in the `Vehicles` object within an environment. The `specify_internal_edge_starts` allows us to do the same to junctions/internal links when they are also located within the network (this is not the case for the ring road).\n",
    "\n",
    "In section 1, we created a network with 4 edges named: \"edge0\", \"edge1\", \"edge2\", and \"edge3\". We assume that the edge titled \"edge0\" is the origin, and accordingly the position of the edge start of \"edge0\" is 0. The next edge, \"edge1\", begins a quarter of the length of the network from the starting point of edge \"edge0\", and accordingly the position of its edge start is radius * pi/2. This process continues for each of the edges. We can then define the starting position of the edges as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import some math functions we may use\n",
    "from numpy import pi\n",
    "\n",
    "class myNetwork(myNetwork):  # update my network class\n",
    "\n",
    "    def specify_edge_starts(self):\n",
    "        r = self.net_params.additional_params[\"radius\"]\n",
    "\n",
    "        edgestarts = [(\"edge0\", 0),\n",
    "                      (\"edge1\", r * 1/2 * pi),\n",
    "                      (\"edge2\", r * pi),\n",
    "                      (\"edge3\", r * 3/2 * pi)]\n",
    "\n",
    "        return edgestarts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Testing the New Network\n",
    "In this section, we run a new sumo simulation using our newly generated network class. For information on running sumo experiments, see `exercise01_sumo.ipynb`.\n",
    "\n",
    "We begin by defining some of the components needed to run a sumo experiment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from flow.core.params import VehicleParams\n",
    "from flow.controllers import IDMController, ContinuousRouter\n",
    "from flow.core.params import SumoParams, EnvParams, InitialConfig, NetParams\n",
    "\n",
    "vehicles = VehicleParams()\n",
    "vehicles.add(veh_id=\"human\",\n",
    "             acceleration_controller=(IDMController, {}),\n",
    "             routing_controller=(ContinuousRouter, {}),\n",
    "             num_vehicles=22)\n",
    "\n",
    "sumo_params = SumoParams(sim_step=0.1, render=True)\n",
    "\n",
    "initial_config = InitialConfig(bunching=40)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For visualizing purposes, we use the environment `AccelEnv`, as it works on any given network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS\n",
    "\n",
    "env_params = EnvParams(additional_params=ADDITIONAL_ENV_PARAMS)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, using the `ADDITIONAL_NET_PARAMS` component see created in section 1.1, we prepare the `NetParams` component."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "additional_net_params = ADDITIONAL_NET_PARAMS.copy()\n",
    "net_params = NetParams(additional_params=additional_net_params)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We are ready now to create and run our network. Using the newly defined network classes, we create a network object and feed it into a `Experiment` simulation. Finally, we are able to visually confirm that are network has been properly generated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from flow.core.experiment import Experiment\n",
    "\n",
    "network = myNetwork(  # we use the newly defined network class\n",
    "    name=\"test_network\",\n",
    "    vehicles=vehicles,\n",
    "    net_params=net_params,\n",
    "    initial_config=initial_config\n",
    ")\n",
    "\n",
    "# AccelEnv allows us to test any newly generated network quickly\n",
    "env = AccelEnv(env_params, sumo_params, network)\n",
    "\n",
    "exp = Experiment(env)\n",
    "\n",
    "# run the sumo simulation for a set number of time steps\n",
    "_ = exp.run(1, 1500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
